// package 节点启动/关闭
package proc

import (
	"bufio"
	"net/http"
	"os"
	"osiris/cli"
	"osiris/config"
	"osiris/core/routine"
	"osiris/core/state"
	"osiris/core/txpool"
	"osiris/dao"
	"osiris/db"
	"osiris/logger"
	"osiris/ocmd"
	"osiris/ocrypto/ed25519"
	"osiris/p2p"
	"osiris/router"
	"sync"
)

// StartProc 启动的routine.RoutineBatch
//
//	@param httpServerPort
//	@param p2pListenPort
//	@param privKeyStr
//	@param isBootstrap
//	@param testMode
//	@return *routine.OrderedRoutineBatch
func StartProc(httpServerPort int, p2pListenPort int, privKeyStr string, isBootstrap bool) *routine.OrderedRoutineBatch {
	mysqlWG := new(sync.WaitGroup)
	keyWG := new(sync.WaitGroup)
	stateWG := new(sync.WaitGroup)
	txPoolWG := new(sync.WaitGroup)
	networkWG := new(sync.WaitGroup)
	cliWG := new(sync.WaitGroup)

	initMySQLRoutine := &routine.OrderedRoutine{
		Operation:    initMySQL,
		Args:         []interface{}{},
		AfterwardWGs: []*sync.WaitGroup{mysqlWG},
	}

	initKeyPairRoutine := &routine.OrderedRoutine{
		Operation:    initKeyPair,
		Args:         []interface{}{privKeyStr},
		AfterwardWGs: []*sync.WaitGroup{keyWG},
	}

	initStateRoutine := &routine.OrderedRoutine{
		Operation:    initState,
		Args:         []interface{}{},
		ForewardWGs:  []*sync.WaitGroup{mysqlWG},
		AfterwardWGs: []*sync.WaitGroup{stateWG},
	}

	initTxPoolRoutine := &routine.OrderedRoutine{
		Operation:    initTxPools,
		Args:         []interface{}{},
		ForewardWGs:  []*sync.WaitGroup{mysqlWG},
		AfterwardWGs: []*sync.WaitGroup{txPoolWG},
	}

	httpServerRoutine := &routine.OrderedRoutine{
		Operation:    runHttpServer,
		Args:         []interface{}{httpServerPort},
		ForewardWGs:  []*sync.WaitGroup{keyWG, txPoolWG, stateWG},
		AfterwardWGs: []*sync.WaitGroup{networkWG},
	}

	p2pNodeRoutine := &routine.OrderedRoutine{
		Operation:    runP2PNode,
		Args:         []interface{}{httpServerPort, p2pListenPort, isBootstrap},
		ForewardWGs:  []*sync.WaitGroup{keyWG, txPoolWG, stateWG},
		AfterwardWGs: []*sync.WaitGroup{networkWG},
	}

	cliRoutine := &routine.OrderedRoutine{
		Operation:    initClientManager,
		Args:         []interface{}{},
		ForewardWGs:  []*sync.WaitGroup{networkWG},
		AfterwardWGs: []*sync.WaitGroup{cliWG},
	}

	commandLineRoutine := &routine.OrderedRoutine{
		Operation:   runCommandLine,
		Args:        []interface{}{},
		ForewardWGs: []*sync.WaitGroup{cliWG},
	}

	routineBatch := routine.OrderedRoutineBatch{}
	return routineBatch.Add(initMySQLRoutine).
		Add(initKeyPairRoutine).
		Add(initStateRoutine).
		Add(initTxPoolRoutine).
		Add(httpServerRoutine).
		Add(p2pNodeRoutine).
		Add(cliRoutine).
		Add(commandLineRoutine)
}

// runHttpServer 启动http服务器，由于在main routine中阻塞等待退出信号，需要最后调用
//
//	@param httpServerPort port for http server, must be different from p2pListenPort
//	@return *http.Server
func runHttpServer(args ...interface{}) {
	//fmt.Println("gin start")
	httpServerPort, isInt := args[0].(int)
	if !isInt {
		logger.Println("Run HTTP server fail due to invalid httpServerPort!")
		return
	}

	router.MakeHttpServer(httpServerPort)

	go func() {
		logger.Info(map[string]interface{}{"Http server": "running"})
		logger.Println("Running http server...")
		if err := router.Server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			logger.Println("Start http server fail due to " + err.Error())
			logger.Error(map[string]interface{}{"[proc] [run Http Sever] start http server": err})
			return
		}
	}()

	//fmt.Println("gin end")
}

// runP2PNode
//
//	@param p2pListenPort port for receiving network stream, which must be different from port for http server
//	@param target example:/ip4/127.0.0.1/tcp/10000/p2p/Qmesr72dy3T21Ro6hTP1mTvi9VC7XZ3T5fSzrgaxVRzCgA
//	@param seed If the seed is zero, use real cryptographic randomness. Otherwise, use a deterministic randomness source to make generated keys stay the same across multiple runs
func runP2PNode(args ...interface{}) {
	//fmt.Println("p2p start")
	httpServerPort, isInt := args[0].(int)
	if !isInt {
		logger.Println("Run P2P Node fail due to invalid httpServerPort!")
		return
	}

	p2pListenPort, isInt := args[1].(int)
	if !isInt {
		logger.Println("Run P2P Node fail due to invalid p2pListenPort!")
		return
	}

	err := p2p.MakeBasicHost(httpServerPort, p2pListenPort)
	if err != nil {
		logger.Println(err)
		logger.Error(map[string]interface{}{"Run P2P node": err.Error()})
		return
	}

	//初始化节点的Multiaddr和ECC 公钥（耗大时）
	go func() {
		p2p.InitPeerStore()
	}()

	//初始化自身节点状态
	go func() {
		p2p.InitSelfState()
	}()

	//启动bootstrap
	isBootstrap, isBool := args[2].(bool)
	if !isBool {
		logger.Println("Run P2P Node fail due to invalid bootstrap option!")
		return
	}
	if isBootstrap {
		p2p.EnableBootstrapProtocol()
	}

	logger.Println("Listening for connections...")
}

// runCommandLine 开启自定义命令行
//
//	@param args
func runCommandLine(args ...interface{}) {
	//http Server和P2P node均开启后再开命令行
	ocmd.PrintCommands()
	logger.DisableStdLog()
	stdReader := bufio.NewReader(os.Stdin)
	ocmd.ReadCommand(stdReader)
}

// initMySQL 初始化MySQL
//
//	@param args
func initMySQL(args ...interface{}) {
	//fmt.Println("mysql start")
	err1 := db.InitMySQL(config.NodeCodeName)
	if err1 != nil {
		logger.Println("Init MySQL fail due to " + err1.Error())
		return
	}

	dao.InitBlock()
	//fmt.Println("mysql end")
}

// initKeyPair 初始化节点的ed25519公私钥对
//
//	@param args
func initKeyPair(args ...interface{}) {
	//fmt.Println("key start")
	privKeyStr, isStr := args[0].(string)
	if !isStr {
		logger.Println("Init Key Pair fail due to invalid privKey string!")
		return
	}
	override := ed25519.InitKeyPair(privKeyStr)

	if override {
		logger.Println("ECC key pair is override by command arg")
		logger.Info(map[string]interface{}{"[proc] [Init Key Pair]": "ECC key pair is override by command arg"})
	} else {
		logger.Println("Use default ed25519 key pair")
		logger.Info(map[string]interface{}{"[proc] [Init Key Pair]": "Use default ed25519 key pair"})
	}

	//fmt.Println("key end")
}

func initTxPools(args ...interface{}) {
	//fmt.Println("txpool start")
	txpool.InitTxPools()
	//fmt.Println("txpool end")
}

func initState(args ...interface{}) {
	//fmt.Println("state start")
	state.InitState()
	//fmt.Println("satte end")
}

func initClientManager(args ...interface{}) {
	cli.InitClientManager()
}
